package com.whosly.foundationdb.study.config.jetty;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.springframework.web.cors.CorsConfiguration.ALL;


/**
 * response
 */
public class WebFluxServerResponseWriter {

    private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

    private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

    private List<ViewResolver> viewResolvers = Collections.emptyList();

    public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
        this.messageReaders = messageReaders;
    }

    public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
        this.messageWriters = messageWriters;
    }

    public void setViewResolvers(List<ViewResolver> viewResolvers) {
        this.viewResolvers = viewResolvers;
    }

    public Runnable redirectAction(ServerWebExchange exchange, String url) {
        return () -> {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.FOUND);
            response.getHeaders().setLocation(URI.create(url));
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALL);
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALL);
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString());
        };
    }

    public Mono<Void> redirect(ServerWebExchange exchange, String url) {
        ServerHttpResponse response = exchange.getResponse();
        if (!response.isCommitted()) {
            response.setStatusCode(HttpStatus.FOUND);
            response.getHeaders().setLocation(URI.create(url));
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALL);
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALL);
            response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString());
            return response.setComplete();
        }
        return Mono.empty();
    }

    public Mono<Void> write(ServerWebExchange exchange, String content) {
        return write(exchange, content, Collections.emptyMap());
    }

    /**
     *
     * @param exchange
     * @param content
     * @param headers  String headerName,  String headerValue
     * @return
     */
    public Mono<Void> write(ServerWebExchange exchange, String content, Map<String, String> headers) {
        Mono<ServerResponse> serverResponseMono = buildApplicationJsonBodyServerResponse(content, headers);

        ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
        return RouterFunctions.route(RequestPredicates.all(), x -> serverResponseMono).route(newRequest)
                .flatMap(handler -> handler.handle(newRequest))
                .flatMap(resp -> writeInternal(exchange, resp));
    }

    public Mono<Void> write(ServerWebExchange exchange, String content, Throwable throwable) {
        Mono<ServerResponse> serverResponseMono = buildApplicationJsonBodyServerResponse(content);
        ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
        return RouterFunctions.route(RequestPredicates.all(), x -> serverResponseMono).route(newRequest)
                .switchIfEmpty(Mono.error(throwable))
                .flatMap(handler -> handler.handle(newRequest))
                .flatMap(resp -> writeInternal(exchange, resp));
    }

    /**
     * 向 resp 中追加  header
     *
     * @param exchange ServerWebExchange
     * @param headers  追加的头map。 存在则不更新
     * @return
     */
    public void addRespHeader(ServerWebExchange exchange, Map<String, String> headers) {
        ServerHttpResponse response = exchange.getResponse();
        if (!response.isCommitted()) {
            if(headers != null && !headers.isEmpty()){
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    if (StringUtils.isEmpty(response.getHeaders().getFirst(entry.getKey()))) {
                        response.getHeaders().set(entry.getKey(), entry.getValue());
                    }
                }
            }
        }
    }

    private Mono<Void> writeInternal(ServerWebExchange exchange, ServerResponse response) {
        exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());
        return response.writeTo(exchange, new ResponseContext());
    }

    private Mono<ServerResponse> buildRedirectServerResponse(String url) {
        return ServerResponse.status(HttpStatus.FOUND)
                .location(URI.create(url))
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALL)
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALL)
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString())
                .build();
    }

    private Mono<ServerResponse> buildApplicationJsonBodyServerResponse(String bodyContent) {
        return buildApplicationJsonBodyServerResponse(bodyContent, Collections.emptyMap());
    }

    private Mono<ServerResponse> buildApplicationJsonBodyServerResponse(String bodyContent, Map<String, String> headers) {
        ServerResponse.BodyBuilder bodyBuilder = ServerResponse.status(HttpStatus.OK)
                .contentType(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALL)
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALL)
                .header(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString());

        if(headers != null && !headers.isEmpty()){
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                bodyBuilder.header(entry.getKey(), entry.getValue());
            }
        }

        return bodyBuilder.body(BodyInserters.fromValue(bodyContent));
    }

    private class ResponseContext implements ServerResponse.Context {
        private ResponseContext() {
        }

        public List<HttpMessageWriter<?>> messageWriters() {
            return messageWriters;
        }

        public List<ViewResolver> viewResolvers() {
            return viewResolvers;
        }
    }
}
